import os
import struct
from typing import Optional, Dict, List, Tuple

class DiskManager:
    """磁盘管理器
    
    负责数据文件的读写操作，管理数据库文件的物理存储
    """
    # 默认页面大小
    PAGE_SIZE = 16 * 1024  # 16KB
    
    def __init__(self, db_file_path: str):
        """初始化磁盘管理器

        Args:
            db_file_path: 数据库文件路径
        """
        self.db_file_path = db_file_path
        self.log_file_path = f"{db_file_path}.log"
        
        # 打开或创建数据库文件
        self._open_db_file()
        
        # 打开或创建日志文件
        self._open_log_file()
        
        # 页面数量
        self.num_pages = self._calculate_num_pages()
    
    def _open_db_file(self) -> None:
        """打开或创建数据库文件"""
        # 确保目录存在
        os.makedirs(os.path.dirname(os.path.abspath(self.db_file_path)), exist_ok=True)

        # 如果文件不存在则先创建，然后打开
        if not os.path.exists(self.db_file_path):
            # 文件不存在时创建文件
            open(self.db_file_path, 'w+b').close()  # 以写入模式创建文件，创建后立刻关闭

        # 文件已创建或已经存在，打开文件进行操作
        self.db_file = open(self.db_file_path, 'r+b')  # 以读写模式打开文件

    def _open_log_file(self) -> None:
        """打开或创建日志文件"""
        # 确保目录存在
        os.makedirs(os.path.dirname(os.path.abspath(self.log_file_path)), exist_ok=True)
        
        # 打开日志文件，如果不存在则创建
        self.log_file = open(self.log_file_path, 'w+b')
    
    def _calculate_num_pages(self) -> int:
        """计算数据库文件中的页面数量"""
        self.db_file.seek(0, os.SEEK_END)
        file_size = self.db_file.tell()
        return file_size // self.PAGE_SIZE

    def _extend_file(self, page_id: int) -> None:
        """扩展数据库文件以容纳指定ID的页面

        Args:
            page_id: 页面ID
        """
        # 计算需要扩展的页面数量
        pages_to_extend = page_id - self.num_pages + 1
        if pages_to_extend <= 0:
            return

        # 定位到文件末尾
        self.db_file.seek(0, os.SEEK_END)

        # 写入空数据以扩展文件
        zeros = bytearray(self.PAGE_SIZE)
        for _ in range(pages_to_extend):
            self.db_file.write(zeros)

        # 更新页面数量
        self.num_pages = page_id + 1

        # 确保数据写入磁盘
        self.db_file.flush()

    def read_page(self, page_id: int) -> Optional[bytearray]:
        """读取指定页面的数据
        
        Args:
            page_id: 页面ID
            
        Returns:
            成功返回页面数据，失败返回None
        """
        # 检查页面ID是否有效
        if page_id >= self.num_pages:
            return None
        
        # 计算页面在文件中的偏移量
        offset = page_id * self.PAGE_SIZE
        
        # 定位到页面位置
        self.db_file.seek(offset)
        
        # 读取页面数据
        page_data = self.db_file.read(self.PAGE_SIZE)
        
        # 如果读取的数据不足一个页面，返回None
        if len(page_data) != self.PAGE_SIZE:
            return None
        
        return bytearray(page_data)
    
    def write_page(self, page_id: int, page_data: bytearray) -> bool:
        """写入页面数据
        
        Args:
            page_id: 页面ID
            page_data: 页面数据
            
        Returns:
            操作是否成功
        """
        # 检查数据大小
        if len(page_data) != self.PAGE_SIZE:
            return False
        
        # 如果页面ID超出当前文件大小，需要扩展文件
        if page_id >= self.num_pages:
            self._extend_file(page_id)
        
        # 计算页面在文件中的偏移量
        offset = page_id * self.PAGE_SIZE
        
        # 定位到页面位置
        self.db_file.seek(offset)
        
        # 写入页面数据
        self.db_file.write(page_data)
        self.db_file.flush()  # 确保数据写入磁盘
        
        return True

    def allocate_page(self) -> int:
        """分配一个新页面
        
        Returns:
            新页面的ID
        """
        page_id = self.num_pages
        self.num_pages += 1
        
        # 扩展文件以容纳新页面
        self._extend_file(page_id)
        
        return page_id
    # 暂时用不上
    def deallocate_page(self, page_id: int) -> bool:
        """释放一个页面
        
        注意：这个实现只是简单地标记页面为未使用，不会实际收缩文件
        
        Args:
            page_id: 要释放的页面ID
            
        Returns:
            操作是否成功
        """
        # 检查页面ID是否有效
        if page_id >= self.num_pages:
            return False
        
        # 实际实现中，这里应该将页面添加到空闲页面列表中
        # 简单起见，我们这里不做实际操作
        
        return True
    # log部分
    def write_log(self, log_data: bytes) -> int:
        """写入日志数据
        
        Args:
            log_data: 日志数据
            
        Returns:
            日志在文件中的偏移量
        """
        # 获取当前日志文件大小（即新日志的偏移量）
        self.log_file.seek(0, os.SEEK_END)
        offset = self.log_file.tell()
        
        # 写入日志长度和数据
        log_len = len(log_data)
        self.log_file.write(struct.pack("<I", log_len))
        self.log_file.write(log_data)
        self.log_file.flush()  # 确保数据写入磁盘
        
        return offset
    
    def read_log(self, offset: int, size: int) -> Optional[bytes]:
        """读取日志数据
        
        Args:
            offset: 日志在文件中的偏移量
            size: 要读取的字节数
            
        Returns:
            日志数据
        """
        # 定位到日志位置
        self.log_file.seek(offset)
        
        # 读取日志数据
        log_data = self.log_file.read(size)
        
        # 如果读取的数据不足指定大小，返回None
        if len(log_data) != size:
            return None
        
        return log_data
    

    def close(self) -> None:
        """关闭数据库文件和日志文件"""
        if hasattr(self, 'db_file') and self.db_file:
            self.db_file.close()
        
        if hasattr(self, 'log_file') and self.log_file:
            self.log_file.close()